﻿using LightCAD.Core;
using LightCAD.Core.Elements;
using LightCAD.Runtime;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Xml.Linq;
using System.Xml.Resolvers;

namespace QdArch
{
    public class QdWall : DirectComponent, IElement3D
    {
        public Line2d BaseLine => (Line2d)this.BaseCurve;
        //TODO 关于默认值 需要在创建时指定，因为需要根据环境給值
        public double Width { get; set; }
        public double Height { get; set; }
        public double Bottom { get; set; }
        public double CenterOffset { get; set; }
        public bool IsReverse { get; set; }

        public QdWall()
        {
            this.Type = ArchElementType.Wall;
        }



        public QdWall(Vector2d start, Vector2d end)
        {
            this.Curves.Add(new Line2d { Start = start, End = end, Source = this });
        }
        public static List<Line2d> GetShapeCurves(Line2d line, double width)
        {

            double x1 = line.Start.X;
            double y1 = line.Start.Y;
            double x2 = line.End.X;
            double y2 = line.End.Y;
            double setoff = (width) / 2;
            Vector2d p1;
            Vector2d p2;
            Vector2d p3;
            Vector2d p4;

            if (x1 == x2)
            {
                p1.Y = y1;
                p1.X = x1 + setoff;
                p2.Y = y1;
                p2.X = x1 - setoff;
                p3.Y = y2;
                p3.X = x2 - setoff;
                p4.Y = y2;
                p4.X = x2 + setoff;
            }
            else
            if (y1 == y2)
            {
                p1.Y = y1 + setoff;
                p1.X = x1;
                p2.Y = y1 - setoff;
                p2.X = x1;
                p3.Y = y2 - setoff;
                p3.X = x2;
                p4.Y = y2 + setoff;
                p4.X = x2;
            }
            else
            {
                double k = (y2 - y1) / (x2 - x1);
                double k1 = -1 / k;
                double b1 = y1 - k1 * x1;



                double xA = x1;
                double yA = y1;
                double xB = x1 + Math.Pow(width, 2);
                double yB = k1 * xB + b1;

                // 给定距离
                double distance = setoff;

                // 计算线段的方向向量
                double directionX = xB - xA;
                double directionY = yB - yA;

                // 计算方向向量的模长
                double length = Math.Sqrt(directionX * directionX + directionY * directionY);

                // 标准化方向向量（单位化）
                double unitDirectionX = directionX / length;
                double unitDirectionY = directionY / length;

                // 计算目标点的坐标
                p1.X = xA + distance * unitDirectionX;
                p1.Y = yA + distance * unitDirectionY;


                Matrix3d matrix3 = Matrix3d.Move(p1, line.Start);
                p2 = matrix3.MultiplyPoint(line.Start);

                matrix3 = Matrix3d.Move(line.Start, line.End);

                p3 = matrix3.MultiplyPoint(p2);
                p4 = matrix3.MultiplyPoint(p1);


                //    double k = (y2 - y1) / (x2 - x1);
                //    double k1 = -1 / k;
                //    double b1 = y1 - k1 * x1;


                //    double sx = setoff / Math.Sqrt(Math.Pow((k1 + b1), 2) + 1);

                //    p1.X = x1 + sx;
                //    p1.Y = k1 * p1.X + b1;
                //    p2.X = x1 - sx;
                //    p2.Y = k1 * p2.X + b1;

                //    double k2 = -1 / k;
                //    double b2 = y2 - k2 * x2;

                //    sx = setoff / Math.Sqrt(Math.Pow((k2 + b2), 2) + 1);

                //    Matrix3 matrix3 = Matrix3.Move(line.Start, line.End);
                ////    this.Set(matrix3.MultiplyPoint(this.Start), matrix3.MultiplyPoint(this.End));

                //    p3 = matrix3.MultiplyPoint(p2);
                //    p4 = matrix3.MultiplyPoint(p1);
                //p4.X = x2 + sx;
                //p4.Y = k2 * p4.X + b2;
                //p3.X = x2 - sx;
                //p3.Y = k2 * p3.X + b2;


                //double y = (x2 - x1);
                //double x= (y1 - y2);


                //p1.X = x1 - setoff * y;
                //p1.Y = y1 + setoff * x;
                //p2.X = x1 + setoff * y;
                //p2.Y = y1 - setoff * x;


                //p3.X = x2 + setoff * y;
                //p3.Y = y2 - setoff * x;
                //p4.X = x2 - setoff * y;
                //p4.Y = y2 + setoff * x;

            }

            List<Line2d> line2Ds = new List<Line2d>();
            Line2d line2D = new Line2d();
            line2D.Start = p1;
            line2D.End = p2;

            line2Ds.Add(line2D);
            line2D = new Line2d();
            line2D.Start = p2;
            line2D.End = p3;

            line2Ds.Add(line2D);
            line2D = new Line2d();
            line2D.Start = p3;
            line2D.End = p4;

            line2Ds.Add(line2D);
            line2D = new Line2d();
            line2D.Start = p4;
            line2D.End = p1;

            line2Ds.Add(line2D);
            return line2Ds;
        }
        public void CreateShapeCurves()
        {
            this.Points.Clear();
            this.Curves.Clear();
            List<Line2d> line2Ds =
            GetShapeCurves(this.BaseLine, this.Width);
            this.Points.Add(line2Ds[1].Start);
            this.Points.Add(line2Ds[1].End);
            this.Points.Add(line2Ds[3].Start);
            this.Points.Add(line2Ds[3].End);
            this.Curves.AddRange(line2Ds);
        }
        public override void ResetShapeCurves()
        {
            //List<Line2d> line2Ds =
            //GetShapeCurves(this.BaseLine, this.Width);
            //this.Points.Add(line2Ds[1].Start);
            //this.Points.Add(line2Ds[1].End);
            //this.Points.Add(line2Ds[3].Start);
            //this.Points.Add(line2Ds[3].End);
            //this.Curves.AddRange(line2Ds);



        }
        public void AddLcComponent(LcElement lcElement, string name, AssociateType type)
        {
            AssociateElement associateElement = new AssociateElement();
            associateElement.Element = lcElement.Clone(); //Clone后Id会发生改变
            associateElement.Tag = lcElement.Id; //将原始的LcElement.Id存下来，方便Remove时来匹配
            associateElement.Type = type;
            this.AssociateElements.Add(associateElement);
            if (name == "Door")
            {
                QdDoorRef door = (QdDoorRef)lcElement;
                Vector2d s1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[0], this.Points[1]);
                Vector2d e1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[2], this.Points[3]);
                Vector2d s2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[0], this.Points[1]);
                Vector2d e2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[2], this.Points[3]);

                Line2d line1 = new Line2d { Start = s1, End = e1 };
                Line2d line2 = new Line2d { Start = s2, End = e2 };
                Dictionary<Line2d, List<Line2d>> dls = new Dictionary<Line2d, List<Line2d>>();
                foreach (Line2d item in this.Curves)
                {

                    if (Line2d.PointInLine(item, s1) && Line2d.PointInLine(item, s2))
                    {
                        dls.Add(item, Line2d.BreakLineBy2line(item, line1, line2));

                    }
                    if (Line2d.PointInLine(item, e1) && Line2d.PointInLine(item, e2))
                    {
                        dls.Add(item, Line2d.BreakLineBy2line(item, line1, line2));

                    }
                }
                foreach (var item in dls)
                {
                    this.Curves.Remove(item.Key); this.Curves.AddRange(item.Value);
                }
                this.Curves.Add(line1); this.Curves.Add(line2);
            }

            if (name == "Window")
            {
                QdWindowRef door = (QdWindowRef)lcElement;
                Vector2d s1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[0], this.Points[1]);
                Vector2d e1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[2], this.Points[3]);
                Vector2d s2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[0], this.Points[1]);
                Vector2d e2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[2], this.Points[3]);

                Line2d line1 = new Line2d { Start = s1, End = e1 };
                Line2d line2 = new Line2d { Start = s2, End = e2 };
                Dictionary<Line2d, List<Line2d>> dls = new Dictionary<Line2d, List<Line2d>>();
                foreach (Line2d item in this.Curves)
                {

                    if (Line2d.PointInLine(item, s1) && Line2d.PointInLine(item, s2))
                    {
                        dls.Add(item, Line2d.BreakLineBy2line(item, line1, line2));

                    }
                    if (Line2d.PointInLine(item, e1) && Line2d.PointInLine(item, e2))
                    {
                        dls.Add(item, Line2d.BreakLineBy2line(item, line1, line2));

                    }
                }
                foreach (var item in dls)
                {
                    this.Curves.Remove(item.Key); this.Curves.AddRange(item.Value);
                }
                this.Curves.Add(line1); this.Curves.Add(line2);
            }

        }
        public void RemoveLcComponent(LcElement lcElement, string name, AssociateType type)
        {
            AssociateElement associateElement = this.AssociateElements.Find(e => (long)e.Tag == lcElement.Id);

            if (associateElement == null)
                return;

            if (name == "Door")
            {
                QdDoorRef door = (QdDoorRef)associateElement.Element;
                Vector2d s1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[0], this.Points[1]);
                Vector2d e1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[2], this.Points[3]);
                Vector2d s2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[0], this.Points[1]);
                Vector2d e2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[2], this.Points[3]);

                Line2d line1 = new Line2d { Start = s1, End = e1 };
                Line2d line2 = new Line2d { Start = s2, End = e2 };

                List<Line2d> rs = new List<Line2d>();
                foreach (Line2d item in this.Curves)
                {

                    if (Line2d.PointInLine(item, door.StartPoint))
                    {
                        rs.Add(item);
                    }
                    if (Line2d.PointInLine(item, door.EndPoint))
                    {
                        rs.Add(item);

                    }
                }
                foreach (var item in rs)
                {
                    this.Curves.Remove(item);
                }
                rs = new List<Line2d>();
                foreach (Line2d item in this.Curves)
                {
                    if (Line2d.PointInLine(item, s1) || Line2d.PointInLine(item, s2) || Line2d.PointInLine(item, e1) || Line2d.PointInLine(item, e2))
                    {
                        rs.Add(item);
                    }
                }
                Dictionary<Line2d, Line2d> dls = new Dictionary<Line2d, Line2d>();
                List<Line2d> cus = new List<Line2d>();
                foreach (Line2d item in rs)
                {
                    foreach (Line2d item2 in rs)
                    {
                        if (item != item2 && Line2d.LineInLine1(item, item2))
                        {
                            if (!cus.Contains(item2) && !cus.Contains(item))
                            {
                                cus.Add(item2); cus.Add(item);
                                dls.Add(item, item2);
                            }
                        }
                    }
                }
                foreach (var item in dls)
                {
                    this.Curves.Remove(item.Key);
                    this.Curves.Remove(item.Value);
                    if (Vector2d.Distance(item.Key.Start, item.Value.Start) > Vector2d.Distance(item.Key.End, item.Value.Start))
                    {
                        if (Vector2d.Distance(item.Key.Start, item.Value.End) > Vector2d.Distance(item.Key.Start, item.Value.Start))
                            this.Curves.Add(new Line2d() { Start = item.Key.Start, End = item.Value.End });
                        else
                            this.Curves.Add(new Line2d() { Start = item.Key.Start, End = item.Value.Start });

                    }
                    else
                    {
                        if (Vector2d.Distance(item.Key.Start, item.Value.End) > Vector2d.Distance(item.Key.Start, item.Value.Start))
                            this.Curves.Add(new Line2d() { Start = item.Key.End, End = item.Value.End });
                        else
                            this.Curves.Add(new Line2d() { Start = item.Key.End, End = item.Value.Start });
                    }
                }

            }

            if (name == "Window")
            {
                QdDoorRef door = (QdDoorRef)associateElement.Element;
                Vector2d s1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[0], this.Points[1]);
                Vector2d e1 = Line2d.GetFootofperpendicular(door.StartPoint, this.Points[2], this.Points[3]);
                Vector2d s2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[0], this.Points[1]);
                Vector2d e2 = Line2d.GetFootofperpendicular(door.EndPoint, this.Points[2], this.Points[3]);

                Line2d line1 = new Line2d { Start = s1, End = e1 };
                Line2d line2 = new Line2d { Start = s2, End = e2 };

                List<Line2d> rs = new List<Line2d>();
                foreach (Line2d item in this.Curves)
                {

                    if (Line2d.PointInLine(item, door.StartPoint))
                    {
                        rs.Add(item);
                    }
                    if (Line2d.PointInLine(item, door.EndPoint))
                    {
                        rs.Add(item);

                    }
                }
                foreach (var item in rs)
                {
                    this.Curves.Remove(item);
                }
                rs = new List<Line2d>();
                foreach (Line2d item in this.Curves)
                {
                    if (Line2d.PointInLine(item, s1) || Line2d.PointInLine(item, s2) || Line2d.PointInLine(item, e1) || Line2d.PointInLine(item, e2))
                    {
                        rs.Add(item);
                    }
                }
                Dictionary<Line2d, Line2d> dls = new Dictionary<Line2d, Line2d>();
                List<Line2d> cus = new List<Line2d>();
                foreach (Line2d item in rs)
                {
                    foreach (Line2d item2 in rs)
                    {
                        if (item != item2 && Line2d.LineInLine1(item, item2))
                        {
                            if (!cus.Contains(item2) && !cus.Contains(item))
                            {
                                cus.Add(item2); cus.Add(item);
                                dls.Add(item, item2);
                            }
                        }
                    }
                }
                foreach (var item in dls)
                {
                    this.Curves.Remove(item.Key);
                    this.Curves.Remove(item.Value);
                    if (Vector2d.Distance(item.Key.Start, item.Value.Start) > Vector2d.Distance(item.Key.End, item.Value.Start))
                    {
                        if (Vector2d.Distance(item.Key.Start, item.Value.End) > Vector2d.Distance(item.Key.Start, item.Value.Start))
                            this.Curves.Add(new Line2d() { Start = item.Key.Start, End = item.Value.End });
                        else
                            this.Curves.Add(new Line2d() { Start = item.Key.Start, End = item.Value.Start });

                    }
                    else
                    {
                        if (Vector2d.Distance(item.Key.Start, item.Value.End) > Vector2d.Distance(item.Key.Start, item.Value.Start))
                            this.Curves.Add(new Line2d() { Start = item.Key.End, End = item.Value.End });
                        else
                            this.Curves.Add(new Line2d() { Start = item.Key.End, End = item.Value.Start });
                    }
                }
            }

            this.AssociateElements.Remove(associateElement);
        }
        public void UpdateLcComponent(LcElement lcElement, string name, AssociateType type)
        {
            RemoveLcComponent(lcElement, name, type);
            AddLcComponent(lcElement, name, type);

            //AssociateElement associateEl = null;
            //foreach (var item in this.AssociateElements)
            //{
            //    if (item.Element == lcElement && item.Type == type)
            //    {
            //        this.AssociateElements.Remove(item);
            //        break;
            //    }
            //}
            //this.AssociateElements.Add(associateElement);
        }
        private List<QdWall> GetCrossWalls(Line2d line2d)
        {
            var walls = Document.ModelSpace.Elements.Where((ele) => ele.Type == ArchElementType.Wall).ToList();
            List<QdWall> lwalls = new List<QdWall>();
            foreach (QdWall wall in walls)
            {
                if (line2d != wall.BaseLine)
                {
                    if (wall.BaseLine.Start.Similarity(line2d.Start) || wall.BaseLine.Start.Similarity(line2d.End) || wall.BaseLine.End.Similarity(line2d.Start) || wall.BaseLine.End.Similarity(line2d.End))
                    {
                        lwalls.Add(wall);
                    }
                    else
                        if (Line2d.CrossLine(line2d, wall.BaseLine))
                    {
                        lwalls.Add(wall);
                    }
                }
            }
            return lwalls;
        }

        public override LcElement ApplyMatrix(Matrix3d matrix)
        {
            BaseLine.Start = matrix.MultiplyPoint(BaseLine.Start);
            BaseLine.End = matrix.MultiplyPoint(BaseLine.End);
            ResetShapeCurves();
            return this;
        }

        public override LcElement Clone()
        {
            var clone = Document.CreateObject<QdWall>();
            clone.Copy(this);
            clone.Initilize(this.Document);
            return clone;

        }

        public override void Copy(LcElement src)
        {
            base.Copy(src);
            var wall = ((QdWall)src);
            this.Curves.Add(wall.BaseLine.Clone());
        }

        public void Set(Vector2d? start = null, Vector2d? end = null, bool fireChangedEvent = true)
        {
            //PropertySetter:Start,End
            if (!fireChangedEvent)
            {
                if (start != null) BaseLine.Start = start.Value;
                if (end != null) BaseLine.End = end.Value;
            }
            else
            {
                bool chg_start = (start != null && start != BaseLine.Start);
                if (chg_start)
                {
                    OnPropertyChangedBefore(nameof(BaseLine.Start), BaseLine.Start, start);
                    var oldValue = BaseLine.Start;
                    BaseLine.Start = start.Value;
                    OnPropertyChangedAfter(nameof(BaseLine.Start), oldValue, BaseLine.Start);
                }
                bool chg_end = (end != null && end != BaseLine.End);
                if (chg_end)
                {
                    OnPropertyChangedBefore(nameof(BaseLine.End), BaseLine.End, end);
                    var oldValue = BaseLine.End;
                    BaseLine.End = end.Value;
                    OnPropertyChangedAfter(nameof(BaseLine.End), oldValue, BaseLine.End);
                }
            }
        }

        protected override void OnPropertyChanged(string propertyName, LcPropertyGroup extPropGroup, LcProperty extProp)
        {
            base.OnPropertyChanged(propertyName, extPropGroup, extProp);
            ResetShapeCurves();
        }
        public override Box2d GetBoundingBox()
        {
            return new Box2d(BaseLine.Start, BaseLine.End);
        }
        public override Box2d GetBoundingBox(Matrix3d matrix)
        {
            return new Box2d(matrix.MultiplyPoint(BaseLine.Start), matrix.MultiplyPoint(BaseLine.End));
        }
        public override bool IntersectWithBox(Polygon2d testPoly, List<RefChildElement> intersectChildren = null)
        {
            var thisBox = this.BoundingBox;
            if (!thisBox.IntersectWith(testPoly.BoundingBox))
            {
                //如果元素盒子，与多边形盒子不相交，那就可能不相交
                return false;
            }
            return GeoUtils.IsPolygonIntersectLine(testPoly.Points, BaseLine.Start, BaseLine.End)
                || GeoUtils.IsPolygonContainsLine(testPoly.Points, BaseLine.Start, BaseLine.End);
        }

        public override bool IncludedByBox(Polygon2d testPoly, List<RefChildElement> includedChildren = null)
        {
            var thisBox = this.BoundingBox;
            if (!thisBox.IntersectWith(testPoly.BoundingBox))
            {
                return false;
            }
            return GeoUtils.IsPolygonContainsLine(testPoly.Points, BaseLine.Start, BaseLine.End);
        }

        public override void Translate(double dx, double dy)
        {
            var ts = new Vector2d(BaseLine.Start.X + dx, BaseLine.Start.Y + dy);
            var te = new Vector2d(BaseLine.End.X + dx, BaseLine.End.Y + dy);
            Set(ts, te);
            ResetBoundingBox();
            ResetShapeCurves();

        }
        public override void Move(Vector2d startPoint, Vector2d endPoint)
        {
            Matrix3d matrix3 = Matrix3d.Move(startPoint, endPoint);
            this.Set(matrix3.MultiplyPoint(BaseLine.Start), matrix3.MultiplyPoint(BaseLine.End));
            ResetBoundingBox();
            ResetShapeCurves();
        }
        public override void Scale(Vector2d basePoint, double scaleFactor)
        {
            Matrix3d matrix3 = Matrix3d.Scale(scaleFactor, basePoint);
            this.Set(matrix3.MultiplyPoint(BaseLine.Start), matrix3.MultiplyPoint(BaseLine.End));
            ResetBoundingBox();
            ResetShapeCurves();
        }
        public override void Scale(Vector2d basePoint, Vector2d scaleVector)
        {

            Matrix3d matrix3 = Matrix3d.Scale(scaleVector, basePoint);
            this.Set(matrix3.MultiplyPoint(BaseLine.Start), matrix3.MultiplyPoint(BaseLine.End));
            ResetBoundingBox();
            ResetShapeCurves();
        }
        public override void Rotate(Vector2d basePoint, double rotateAngle)
        {
            Matrix3d matrix3 = Matrix3d.RotateInRadian(rotateAngle, basePoint);
            this.Set(matrix3.MultiplyPoint(BaseLine.Start), matrix3.MultiplyPoint(BaseLine.End));
            ResetBoundingBox();
            ResetShapeCurves();
        }
        public override void Mirror(Vector2d axisStart, Vector2d axisEnd)
        {
            Matrix3d matrix3 = Matrix3d.Mirror(axisEnd, axisStart);
            this.Set(matrix3.MultiplyPoint(BaseLine.Start), matrix3.MultiplyPoint(BaseLine.End));
            ResetBoundingBox();
            ResetShapeCurves();
        }
        public override void WriteProperties(Utf8JsonWriter writer, JsonSerializerOptions soptions)
        {
            base.WriteProperties(writer, soptions);

            writer.WriteNumberProperty(nameof(Width), this.Width);

            if (this.Height != 0)
            {
                writer.WriteNumberProperty(nameof(Height), this.Height);
            }
            if (this.Bottom != 0)
            {
                writer.WriteNumberProperty(nameof(Bottom), this.Bottom);
            }
            if (this.CenterOffset != 0)
            {
                writer.WriteNumberProperty(nameof(CenterOffset), this.CenterOffset);
            }
            if (this.IsReverse)
            {
                writer.WriteBoolProperty(nameof(IsReverse), this.IsReverse);
            }
        }
        public override void ReadProperties(ref JsonElement jele)
        {
            base.ReadProperties(ref jele);

            this.Width = jele.ReadDoubleProperty(nameof(Width));

            this.Height = jele.ReadDoubleProperty(nameof(Height));
            this.Bottom = jele.ReadDoubleProperty(nameof(Bottom));
            this.CenterOffset = jele.ReadDoubleProperty(nameof(CenterOffset));

            var isRev = jele.ReadBoolProperty(nameof(IsReverse));
            if (isRev != null) this.IsReverse = isRev.Value;
        }
    }
}